#include <iostream>
#include <cstdlib>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <ctime>
#include <sys/time.h>
#include <omdcl/machine.hpp>
#include <omdcl/program.hpp>
#include <omd/paramhandler.hpp>

using std::string;
using std::vector;
using std::ifstream;
using std::ofstream;
using std::istringstream;

class GpuRunClass: public MDToolkit {
	ParamHandler param;
	string program_filename;
	
	MDComputeMachine *machine;
	MDComputeProgram *program;
	
	vector<float*> data;
	vector<size_t> datasize;
	vector<cl_mem> devdata;
	float* result;
	cl_uint resultsize;
	int n_repeat;
	int extract_every;
	int global[3], local[3];
	bool printtime;

public:

    void read_input(string filename) {
        ParamHandler p;
        int nfield;
        
        istringstream str(replace_char(filename,':',' '));
        str >> filename >> nfield;
        assert(file_exist(filename), "can not find input file ("+filename+")");
        
        p.read_pseudo(filename);
        vector<float> indat;
        
        ifstream infile(filename.c_str());
        char sline[2048]; // max 2047 characters per line!!

        while(infile.good()) {
            memset(sline,0,2048);
            infile.getline(sline,2048);
            istringstream istr(sline);
            
            if(infile.good()) {
                float ff;
                for(int fld=1;fld<nfield;fld++) istr>>ff;
                if(!(istr>>ff)) die("error reading data");
                indat.push_back(ff);
            }
            
        }
        
        float* dd=new float[indat.size()*sizeof(float)];
        for(int i=0;i<(int)indat.size();i++) dd[i]=indat[i];
        data.push_back(dd);
        datasize.push_back(indat.size());
    }
    
    void print_result() {
    	if(param.string_value("output")=="stdout"){
	    	for(size_t i=0; i<resultsize;i++) 
	    		std::cout << result[i] << std::endl;
		} else if(param.string_value("output")=="stderr") {
			for(size_t i=0;i<resultsize;i++)
				std::cerr<<result[i]<<std::endl;
		} else {
			ofstream ofl(param.string_value("output").c_str());
			assert(ofl.good(), "faild to open file for output");
			for(size_t i=0;i<resultsize;i++) 
				ofl<<result[i]<<std::endl;			
		}
	}
				
    void run() {
        if(param.exist("input")) {
            string input_fl=param.string_value("input");
            read_input(input_fl);
        } else {
        	int n=1;
        	while(param.exist("input."+as_string(n))) {
            	string input_fl=param.string_value("input."+as_string(n++));
            	read_input(input_fl);
        	}
        }
        
        for(size_t i=0;i<data.size();i++) {
        	cl_mem cmem=machine->LoadTable(data[i], sizeof(float)*datasize[i]);
        	devdata.push_back(cmem);
			program->PushArgument(sizeof(cl_mem),&(devdata[i]));
			
		}		

		
		// FIXME! only one dimension
		program->SetDimension(global[0]);
		resultsize=global[0];

		result=new float[resultsize];
		cl_mem devresult=machine->CreateBuffer(sizeof(float)*resultsize);
		program->PushArgument(sizeof(cl_mem), &devresult);
		program->PushArgument(sizeof(cl_uint), &resultsize);
		
		
		timeval tstart, tend;
		gettimeofday(&tstart,NULL);
		for(int rep=0;rep<n_repeat;rep++) {
			program->Execute();
			machine->Wait();
			if(!(rep%extract_every)) {
				machine->Extract(devresult, result, sizeof(float)*resultsize);
				print_result();
			}
		}
		gettimeofday(&tend,NULL);
		
		if(printtime) 
		    std::cerr << "total_execution_time:"
		    <<(float)((tend.tv_sec-tstart.tv_sec)+1e-6*(tend.tv_usec-tstart.tv_usec))
		    <<" seconds"<<std::endl;
		
    }
    
    void init() {		
		assert(param.size()>1, "gpu program filename needed");
		program_filename=param[1];
		assert(file_exist(program_filename), "can not find ("+program_filename+")");
		param.set_pseudo("//$");
		param.read_pseudo(program_filename);
		machine=new MDComputeMachine(CL_DEVICE_TYPE_GPU);
		program=new MDComputeProgram(machine, program_filename);
		program->SelectKernel("clmain");
		resultsize=0;
		param.peek("repeat", n_repeat, 1);
		param.peek("extract_every", extract_every, 1);
		if(param.exist("--time")) printtime=true;
		else printtime=false;
		
		global[0]=param.int_value("dim.global",0);
		global[1]=param.int_value("dim.global",1);
		global[2]=param.int_value("dim.global",2);
		local[0]=param.int_value("dim.local",0);
		local[1]=param.int_value("dim.local",1);
		local[2]=param.int_value("dim.local",2);
		
	}

	GpuRunClass(int& argc,char** &argv) {
	    set_name("gpurun");
		param.assign(argc,argv);
		result=NULL;
		program=NULL;
		machine=NULL;
		if(param.exist("--list")) {
			vector<string> dlist=MDComputeMachine::GetDeviceList();
			std::cout<<"available devices:\n";
			for(int i=0;i<(int)dlist.size();i++) {
            	std::cout<<"("<<i+1<<") "<<dlist[i]<<std::endl;
			}
		} else {
		    init();
		    run();
		}
    }
	
	~GpuRunClass(){
		for(size_t i=0;i<data.size();i++) delete data[i];
		if(result)  delete result;
	    if(program) delete program;
	    if(machine) delete machine;
	}
	
};

// reserved for md_toolkit...
ofstream blogstream("gpurun.log");
ofstream memlogstream("mem.log");

int main(int argc, char* argv[]) {
	int retval=EXIT_SUCCESS;
	try {
		GpuRunClass gpurun(argc,argv);			
	} catch(const char* errst) {
		std::cerr << errst << std::endl;
		retval=EXIT_FAILURE;
	} catch(...) {
		std::cerr << "unknown error...\n";
		retval=EXIT_FAILURE;
	}
	
	return retval;
}